#define _CRT_SECURE_NO_WARNINGS 1


		《 Linux_20_IO文件 》


我们要进行文件操作，前提是我们的程序跑起来了。
文件打开和关闭，是CPU在执行我们的代码

打开文件：本质是进程打开文件！！！

log.txt(创建文件)  Makefile  testfile  testfile.c
[a@192 a]$ pwd
home/a/Desktop/a/b/a		//执行程序，都会变成进程，程序所在当前路径

[a@192 a]$ cat testfile.c
#include <stdio.h>

int main()
{
	FILE* fp = fopen("log.txt", "w");
	if (NULL == fp)
	{
		perror("fopen");
		return 1;
	}

	fclose(fp);
	return 0;
}

先描述，再组织：
文件没有被打开的时候，在哪里?		磁盘存在
进程能打开很多文件吗?				可以
 
系统中可不可以存在很多进程呢 ? ?
很多情况下，OS内部，一定存在大量的被打开的文件

OS 如何管理这些被打开的文件呢？
每一个被打开的文件，在OS内部，一定要存在对应的描述文件属性的结构体。类似PCB !

打开文件 与被 打开的内容一定的 是两种对象之间的指针关系。
struct task_struct -> struct XXX?

新建一个文件也是要占空间的：文件的的属性
文件 = 属性 + 内容

————————————————————————————————————————————————————————————

#include <stdio.h>

int main()
{
    FILE* fp = fopen("log.txt", "a");		//追加（appending）  
    if (NULL == fp)
    {
        perror("fopen");
        return 1;
    }
    fprintf(fp, "hello world! %d, %s, %lf\n", 10, "abc", 3.14);

    fclose(fp);
    return 0;
}

[a@192 a]$ cat log.txt
hello world!10, abc, 3.140000

输出重定向 '>' ： 是文件操作的 "W" 方式操作。
[a@192 a]$ echo "hello bite" > log.txt
[a@192 a]$ cat log.txt
hello bite

 ' > '输出重定向 可以：
1.新建文件
2.清空文件

[a@192 a]$ echo "hello bite" >> log.txt
[a@192 a]$ echo "hello bite" >> log.txt
[a@192 a]$ cat log.txt
hello bite
hello bite

 ' >> '追加重定向 可以：
1.新建文件
2.追加写文件


————————————————————————————————————————————————————————————


————————————————————————————————————————————————————————————


		《 Linux_21_文件描述符 》

open 打开文件写，默认不清空内容。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char* pathname, int flags);
int open(const char* pathname, int flags, mode_t mode);   //打开文件（要打开的文件名， 以什么方式打开， 起始权限）
一个文件打开后会返回一个数字(文件描述符)，失败返回 -1 错误码被设置。

int creat(const char* pathname，mode_t mode);


————————————————————————————————————————————————————————————

  C/C++ 向文件写
#include <iostream>
#include <string.h>
#include <fstream>

#define FILENAME "log.txt"

int main()
{
	//std::ofstream out(FILENAME, std::ios::binary);	//二进制
	std::ofstream out(FILENAME);		//打开文件流
	if (!out.is_open())return 1;

	std::string message = "hello C++\n";//写入
	out.write(message.c_str(), message.size()); 
	out.close();						//关闭

	return 0;
}


————————————————————————————————————————————————————————————


      < 打开文件 >

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    //system call 系统调用
    //打开一个文件，以写的方式打开，如果不存在，创建这个文件
    int fd = open("log.txt", O_WRONLY | O_CREAT);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }

    return 0;
}

创建 log.txt 成功了，但是权限是乱码
【--wsr - s--T】. 1 a a    0 Apr 18 09:23 log.txt

————————————————————————————————————————————————————————————

int main()
{
    umask(0);   //取消系统默认掩码！ 
    //system call 系统调用
    //打开一个文件，以写的方式打开，如果不存在，创建这个文件， 初始权限为 666 == rw- rw- rw-
    int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);     // 【 O_WRONLY | O_CREAT 】位图，按二进制位来使用这个 int .
    if (fd < 0)
    {
        perror("open");
        return 1;
    }
    return 0;
}

指定初始权限：
int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
期望权限：【666】 ==> 【664】 实际权限
-rw - rw - r--. 1 a a    0 Apr 18 09:31 log.txt
[a@192 a]$ umask          //原因是 掩码 的问题。
0002

umask(0);   //取消系统默认掩码后 重新打开文件权限就是所期望的。
-rw - rw - rw - . 1 a a    0 Apr 18 09:40 log.txt


————————————————————————————————————————————————————————————


————————————————————————————————————————————————————————————


			《 标记位传参 》

常见的标记位有：
只读：O_RDONLY，
只写：O_WRONLY,
读写：O_RDWR.


#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define ONE 1        // 1: 0000 0001 
#define TWO   (1<<1) // 2: 0000 0010
#define THREE (1<<2) // 3: 0000 0100
#define FOUR  (1<<3) // 4: 0000 1000

void print(int flag)
{
    if (flag & ONE)    printf("one\n");
    if (flag & TWO)    printf("two\n");
    if (flag & THREE)  printf("three\n");
    if (flag & FOUR)   printf("four\n");
}

int main()
{
    print(ONE);					printf("\n");	//替换为其他功能。
    print(TWO);					printf("\n");
    print(ONE | TWO | THREE);	printf("\n");
    print(TWO | FOUR);			printf("\n");
    print(ONE | TWO | THREE | FOUR); printf("\n");

    return 0;
}

结果：
one
two
one two three
two four
one two three four


————————————————————————————————————————————————————————————


————————————————————————————————————————————————————————————

			《 文件的 r w a 方式打开文件 》

#include <unistd.h>
ssize_t write(int fd, const void* buf， size_t count);


int main()
{
    umask(0);   //取消系统默认掩码！
    //system call 系统调用

		< r 方式 覆盖写 >
    //打开一个文件，以写的方式打开，如果不存在，创建这个文件初始权限为666
    int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);			//1.open 打开文件写，默认不清空内容。

		< w 方式 清空写 >
    //打开一个文件，    以写的方式打开|如果不存在，创建这个文件|打开并清空文件， 初始权限为666
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);	//3.并截断清空打开文件。

		< a 方式 追加写 >
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);//4.追加写。

    if (fd < 0)
    {
        perror("open");
        return 1;
    }
    //const char* message = "hello Linux file!\n";				//1.
    const char* message = "1234";									//2.

    write(fd, message, strlen(message));//只写有效数据

    return 0;
}

打开文件结果：
1.
 O_WRONLY | O_CREAT
以写的方式打开，如果不存在，创建这个文件
[a@192 a]$ cat log.txt
hello Linux file!

open 打开文件写，默认不清空内容。
2.【覆盖写】
从新写入1234 
[a@192 a]$ cat log.txt
1234Linux file!

3.【清空写】
 O_TRUNC  截断:truncated == w
[a@192 a]$ cat log.txt
1234

4.【追加写】
O_APPEND 追加写 == a
[a@192 a]$ cat log.txt
1234 1234 1234 1234

————————————————————————————————————————————————————————————


————————————————————————————————————————————————————————————


		《 返回值 》

文件描述符返回值

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{

	int fda = open("loga.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); 
	printf("fda: %d\n", fda);	//打开并清空文件
	int fdb = open("logb.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); 
	printf("fdb: %d\n", fdb);
	int fdc = open("logc.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); 
	printf("fdc: %d\n", fdc);
	int fdd = open("logd.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	printf("fdd: %d\n", fdd);
	int fde = open("loge.txt", O_WRONLY | O_CREAT | O_APPEND, 0666); 
	printf("fde: %d\n", fde);//4.追加写。

	return 0;
}

结果：没有 0 1 2  
[a@192 a]$ . / myfile
fda : 3	==> 文件描述符
fdb : 4
fdc : 5
fdd : 6
fde : 7

[a@192 a]$ ll
total 24
- rw - rw - r--. 1 a a    0 Apr 20 05 : 01 loga.txt
- rw - rw - r--. 1 a a    0 Apr 20 05 : 01 logb.txt
- rw - rw - r--. 1 a a    0 Apr 20 05 : 01 logc.txt
- rw - rw - r--. 1 a a    0 Apr 20 05 : 01 logd.txt
- rw - rw - r--. 1 a a    0 Apr 20 05 : 01 loge.txt


结果：没有 0 1 2  
#include <stdio.h>
extern FILE* stdin; // 0:标准输入 键盘   都是 FILE*
extern FILE* stdout;// 1:标准输出 显示器
extern FILE* stderr;// 2:标准错误 显示器


————————————————————————————————————————————————————————————

		《 默认打开的 3 个标准流 》

int main()
{
    fprintf(stdout, "hello: %d\n", 10);
    fflush(stdout); //数据刷新
    return 0;
}
[a@192 a]$ . / myfile
hello : 10


int main()
{
	const char* message = "hello Linux file!\n";
	write(1, message, strlen(message));	//向标准输出流写 (Linux 一切皆文件)
	return 0;
}
[a@192 a]$ . / myfile
hello Linux file!


 vfs :virtual file system 虚拟文件系统：

【      task_struct      】    【   struct file_struct  】    【struct file】    无论读写，都必须在合适的时候让
[                         ]     [文件描述符, 数组下标: 0 ]     [被打开的文件]    OS把文件的内容读到文件缓冲区中
[structfiles_struct* files] --> [struct file* fd_array[N]] --> [   ↑↓     ]    -->   【文件内核级缓存】   -->    【磁盘】
[                         ]     [                      2 ]     [用双链表链接]
                                                             每一个被打开的文件还要有
                                                             一个操作底层方法的 [指针表]

open在干什么呢 ? 
1.创建 file
2.开辟文件缓冲区的空间，加载文件数据(延后)
3.查进程的文件指述符表
4.file地址，填入对应的表下标中
5.返回下标



————————————————————————————————————————————————————————————

在 OS 内，系统在访问文件的时候，只认文件描述符 fd  

		< FILE >

如果有兴趣，可以看看FILE结构体:
typedef struct _IO_FILE FILE;
在 /usr/include/stdio.h
在 /usr/include/libio.h
struct _IO_FILE { ... ... };


#include <stdio.h>
size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream);

             ("hello"要写内容, 5 要写数量, 1 每个大小, fp 往哪里写);
size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream);

文件描述符表 ==> 封装
int main()
{
	printf("stdin->fd: %d\n", stdin->_fileno);		// 0 标准输入
	printf("stdout->fd: %d\n", stdout->_fileno);	// 1 标准输出
	printf("stderr->fd: %d\n", stderr->_fileno);	// 2 标准错误

 
	FILE* fp = fopen("log.txt", "w");
	if (fp == NULL)return 1;
	printf("fd: %d\n", fp->_fileno);	//打印文件描述符 3

	FILE* fp1 = fopen("log1.txt", "w");
	if (fp1 == NULL)return 1;
	printf("fd1: %d\n", fp1->_fileno);	//打印文件描述符 4

	FILE* fp2 = fopen("log2.txt", "w");
	if (fp2 == NULL)return 1;
	printf("fd2: %d\n", fp2->_fileno);	//打印文件描述符 5

	fwrite("hello", 5, 1, fp);
	fclose(fp);			

	return 0;
}

所有的 C语言 上的文件操作函数，本质底层都是对系统调用的封装！！！

我们可以使用系统调用，也可以使用 语言提供的文件操作方法。
为什么要对系统调用封装呢？

因为系统不同，系统调用接口可能不一样。
一个系统写的代码 无法在另一个系统中运行(代码不具备跨平台性)

于是不同系统都对 C标准库 进行封装
写出来的代码才具备跨平台性
不同语言在封装的时候，文件接口就有差别了。


————————————————————————————————————————————————————————————


————————————————————————————————————————————————————————————


			《 向终端写入 》

 ls /proc/24306	打开进程所有的属性信息。

lrwXrwXrwx 1 whb whb 0 Mar 26 21:38 
cwd -> /home/whb/111/code/lesson19/file
pid : 24306

- r-------- 1 whb whb 0 Mar 26 21 : 39 environ
pid : 24306
lrwxrwxrwx 1 whb whb 0 Mar 26 21 : 38 
exe -> /home/whb/111/code/1esson19/file/myfile
pid : 24306

fd
pid : 24306

[a@192 a]$. ls /proc/24306/fd -l 
0 -> /dev/pts/4					//默认文件描述符 0 1 2 是被打开的。
1 -> /dev/pts/4					//打开的终端【窗口内容： pid : 24306】
2 -> /dev/pts/4					//于是我们可以直接向终端进行写入。


#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
	int fd = open("/dev/pts/4", O_WRONLY | O_APPEND);		//向终端 /dev/pts/4 中写 
	if (fd < 0) return 1;

	const char* message = "hahaha\n";
	while (1)
	{
		write(fd, message, strlen(message));
		sleep(1);
	}
	close(fd);
	
	//while (1) 
	//{
	//	printf("pid: %d\n", getpid());	//获取进程 PID
	//	sleep(1);
	//}

	return 0;
}


		/dev/tty/  虚拟机终端
		/dev/pts/  云端终端
[a@192 a]$. echo "helloworld" > /dev/pts/7		//指向向终端进行写操作。


————————————————————————————————————————————————————————————


————————————————————————————————————————————————————————————

